use core::arch::{asm, naked_asm};

use crate::{debug, println};

#[unsafe(naked)]
pub unsafe extern "C" fn primary_entry(_fdt_addr: *mut u8) -> ! {
    naked_asm!(
        // Save dtb address.
        "MOV      x19, x0",
        // Set the stack pointer.
        "ADRP     x1,  __kernel_code_end",
        "ADD      x1, x1, :lo12:__kernel_code_end",

        "BL       {vma}",
        "mov      x20, x0",
        "BL       {lma}",
        "sub      x20, x20, x0",
        "BL       {_start_lma}",
        "mov      x21, x0",

        "MOV      x0, x19",
        "LDR      x2, ={virt_entry}",
        "MOV      x3, x20",    // va offset
        "mov      x4, x21",    // kernel start lma,
        "ADRP     x20,  {entry}",
        "ADD      x20, x20, :lo12:{entry}",
        "BR       x20",
        "B        .",
        entry = sym LOADER_BIN,
        virt_entry = sym virt_entry,
        vma = sym entry_vma,
        lma = sym entry_lma,
        _start_lma = sym _start_lma,
    )
}

macro_rules! loader_bin_slice {
    () => {
        include_bytes!(concat!(env!("OUT_DIR"), "/loader.bin"))
    };
}

const LOADER_BIN_LEN: usize = loader_bin_slice!().len();

const fn loader_bin() -> [u8; LOADER_BIN_LEN] {
    let mut buf = [0u8; LOADER_BIN_LEN];
    let bin = loader_bin_slice!();
    buf.copy_from_slice(bin);
    buf
}

#[unsafe(link_section = ".loader")]
static LOADER_BIN: [u8; LOADER_BIN_LEN] = loader_bin();

fn virt_entry(dtb: *mut u8, tmp_boot_table_end: usize) {
    unsafe extern "C" {
        fn __vma_relocate_entry(dtb: *mut u8, tmp_boot_table_end: usize);
    }
    unsafe {
        debug::fdt::init_debugcon(dtb);

        debug::write_byte(b'B');
        debug::write_byte(b'\r');
        debug::write_byte(b'\n');


        println!("ok {:p}", virt_entry as *const ());

        __vma_relocate_entry(dtb, tmp_boot_table_end);
    }
}

#[unsafe(naked)]
unsafe extern "C" fn entry_lma() -> usize {
    naked_asm!(
        "ADRP     x0,  __vma_relocate_entry",
        "ADD      x0, x0, :lo12:__vma_relocate_entry",
        "ret"
    )
}

#[unsafe(naked)]
unsafe extern "C" fn entry_vma() -> usize {
    naked_asm!(
        "
    LDR  x0,  =__vma_relocate_entry
    ret
    "
    )
}

#[unsafe(naked)]
unsafe extern "C" fn _start_lma() -> usize {
    naked_asm!(
        "ADRP     x0,  _start",
        "ADD      x0, x0, :lo12:_start",
        "ret"
    )
}
